package com.domino.client.mvc.impl;

import java.util.List;

import com.domino.client.OthelloServiceAsync;
import com.domino.client.mvc.Controller;
import com.domino.client.mvc.ViewUpdateCallback;
import com.domino.server.cache.State;
import com.domino.shared.ActionResponse;
import com.domino.shared.JoinRequest;
import com.domino.shared.JoinResponse;
import com.domino.shared.LeaveGameReason;
import com.domino.shared.Player;
import com.domino.shared.PutRequest;
import com.domino.shared.SessionStateRequest;
import com.domino.shared.SessionStateResponse;
import com.domino.shared.model.Brick;
import com.domino.shared.model.Color;
import com.domino.shared.model.Point;
import com.domino.shared.model.SimpleBoard;
import com.domino.shared.session.SessionId;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;

public class ControllerImpl implements Controller {

	private OthelloServiceAsync service;
	private SessionId sessionId;
	private Player player;
	private Color clientColor;
	

	public ControllerImpl(OthelloServiceAsync service) {
		this.service = service;
	}

	public SessionId getSessionId() {
		return sessionId;
	}

	public void setSessionId(SessionId sessionId) {
		this.sessionId = sessionId;
	}

	public Player getPlayerId() {
		return player;
	}

	public void setPlayerId(Player playerId) {
		this.player = playerId;
	}

	@Override
	public void joinGame(int size, ViewUpdateCallback callback) {
		joinGame(size, callback, true);
	}

	@Override
	public void joinGameAnonymously(int size, ViewUpdateCallback callback) {
		joinGame(size, callback, false);
	}

	@Override
	public void poll(int nofBricks, final ViewUpdateCallback callback) {
		SessionStateRequest request = new SessionStateRequest(sessionId,
				player, nofBricks);
		service.pollSession(request, new AsyncCallback<SessionStateResponse>() {

			@Override
			public void onSuccess(SessionStateResponse result) {
				updateSession(result.getSessionId());
				updateColor(result.getColor());
				callback.notify(result);
			}

			private void updateColor(Color color) {
				if (color != null) {
					ControllerImpl.this.clientColor = color;
				}
			}

			private void updateSession(SessionId sessionId) {
				if (sessionId != null) {
					ControllerImpl.this.sessionId = sessionId;
				}
			}

			@Override
			public void onFailure(Throwable caught) {
				callback.notifyError(true, caught.getLocalizedMessage());
				startInGameErrorHandlingTimer(callback, 3);
			}
		});
	}

	@Override
	public void putBrick(Point move, final ViewUpdateCallback callback) {
		PutRequest request = new PutRequest(this.sessionId, 1, new Brick(move,
				clientColor));
		service.putBrick(request, new AsyncCallback<ActionResponse>() {

			@Override
			public void onSuccess(ActionResponse result) {
				callback.notify(new SessionState(SessionState.WAIT_FOR_SERVER,
						null, null, null, null, null, null, null));
			}

			@Override
			public void onFailure(Throwable caught) {
				callback.notifyError(true, caught.getLocalizedMessage());
				startInGameErrorHandlingTimer(callback, 3);
			}
		});
	}

	private void startInGameErrorHandlingTimer(
			final ViewUpdateCallback callback, final int numberOfTries) {
		if (numberOfTries > 0) {
			Timer pollSessionTimer = new Timer() {

				@Override
				public void run() {
					service.getBricks(sessionId,
							new AsyncCallback<List<Brick>>() {

								@Override
								public void onSuccess(List<Brick> result) {
									callback.notify(createSession(result));
									callback.notify(new SessionState(
											SessionState.WAIT_FOR_SERVER, null,
											null, null, null, null, null, null));
								}

								private SessionStateResponse createSession(
										List<Brick> result) {
									return new SessionState(
											SessionState.RECOVERING, null,
											null, result, null, null, null, null);
								}

								@Override
								public void onFailure(Throwable caught) {
									startInGameErrorHandlingTimer(callback,
											numberOfTries - 1);
								}
							});
				}
			};

			int ERROR_HANDLING_MILLIS = 5000;
			pollSessionTimer.schedule(ERROR_HANDLING_MILLIS);
		}
	}

	private void joinGame(final int size, final ViewUpdateCallback callback,
			boolean requestLogin) {
		service.joinGame(new JoinRequest(size, requestLogin),
				new AsyncCallback<JoinResponse>() {

					@Override
					public void onSuccess(JoinResponse result) {
						sessionId = result.getSessionId();
						clientColor = result.getColor();
						Player opponent = retrievePlayers(
								result.getPlayerBlack(),
								result.getPlayerWhite());
						int state = (opponent == null) ? SessionState.WAIT_FOR_OPPONENT_TO_JOIN
								: SessionState.WAIT_FOR_SERVER;

						callback.notify(new SessionState(state, player,
								opponent, null, sessionId, clientColor, null,
								""));
					}

					private Player retrievePlayers(Player playerBlack,
							Player playerWhite) {
						player = Color.BLACK.equals(clientColor) ? playerBlack
								: playerWhite;
						return getPlayerId().equals(playerBlack) ? playerWhite
								: playerBlack;
					}

					@Override
					public void onFailure(Throwable caught) {
						callback.notifyError(false, caught
								.getLocalizedMessage());
					}

				});
	}

	@Override
	public void leaveGame(SessionId sessionId, final ViewUpdateCallback callback) {
		service.leaveGame(sessionId, player.getId(), LeaveGameReason.DontWantToPlay, new AsyncCallback<Void>() {
			
			@Override
			public void onSuccess(Void result) {
				callback.notifyPlayerGaveUp(clientColor);
			}
			
			@Override
			public void onFailure(Throwable caught) {
				callback.notifyError(false, caught.getLocalizedMessage());
			}
		});
	}
	
	@Override
	public void setBotController(int size) {
		OthelloServiceAsync tempService = service;
		service = new AutomaticOthelloServiceAsync(this.sessionId, size, this.player.getId(), clientColor, tempService);
		tempService.leaveGame(this.sessionId, this.player.getId(), LeaveGameReason.PlayWithBot, new AsyncCallback<Void>() {
			
			@Override
			public void onSuccess(Void result) {
				//do nothing
			}
			
			@Override
			public void onFailure(Throwable caught) {
				//do nothing
			}
		});
	}

}
